from datetime import datetime

class MalformedFormatString (BaseException):
	def __init__ (filename):
		self.filename = filename

class TimeSeries:
	def __init__ (self, multiple=False):
		""" Creates a new timeseries. Use multiple=true for a combined dataset. """
		self.data = []
	def get_position (self,date):
		lo = 0
		hi = len(self.data)-1
		while self.data[lo][0] < self.data[hi][0]:
			mid = (lo+hi)//2
			midval = self.data[mid][0]
			if midval < date: lo = mid+1
			elif midval > date: hi = mid
			else: return mid
		return lo

def process_header (file):
	"""Read the header from a timeseries file and return a new TimeSeries object with the appropriate metadata.
	"""
	ts = TimeSeries ()
	line = file.readline ()
	while not line == "\n":
		words = line.split (": ")
		words[1] = words[1].strip ("\r\n")
		if words[0] == "Name": ts.name = words[1]
		elif words[0] == "Start": ts.start = datetime.strptime (words[1], "%Y-%m-%d")
		elif words[0] == "End": ts.end = datetime.strptime (words[1], "%Y-%m-%d")
		elif words[0] == "Created": ts.created = datetime.strptime (words[1], "%Y-%m-%d")
		elif words[0] == "X-Units": ts.x_units = words[1]
		elif words[0] == "Y-Units": ts.y_units = words[1]
		elif words[0] == "Author": ts.author = words[1]
		elif words[0] == "Source": ts.source = words[1]
		elif words[0] == "Source-URL": ts.source_url = words[1]
		elif words[0] == "Source-Article": ts.source_article = words[1]
		elif words[0] == "Source-Section": ts.source_section = words[1]
		elif words[0] == "Source-Table": ts.source_table = words[1]
		elif words[0] == "Data-Mode": ts.data_mode = words[1]
		elif words[0] == "Tags": ts.tags = words[1].split (" ")
		line = file.readline ()
	ts.header_length = file.tell ()
	line = file.readline ()
	ts.line_length = len(line)
	file.seek (ts.header_length)
	return ts

def get_time_series (filename, start, end, include_times=True):
	"""Return an array of (time,value) coordinates

	Keyword arguments:
	start -- the start time in datetime format
	finish -- the finish time in the same format
	include_times -- False returns only the values over the time series
	"""
	file = open(filename,'rU')
	ts = process_header (file)
	ts.filename = filename
	if ts.x_units == "years":
		units_until_start_time = start.year - ts.start.year
		units_until_end_time = end.year - start.year
	elif ts.x_units == "months":
		units_until_start_time = (start.year-ts.start.year)*12 + start.month - ts.start.month
		units_until_end_time = (end.year-start.year)*12 + end.month - start.month
	elif ts.x_units == "days":
		units_until_start_time = (start-ts.start).days
		units_until_end_time = (end-start).days
	elif ts.x_units == "weeks":
		units_until_start_time = (start-ts.start).days/7
		units_until_end_time = (end-start).days/7
	elif ts.x_units == "quarters":
		units_until_start_time = ((start.year-ts.start.year)*12+start.month-ts.start.month)/3
		units_until_end_time = ((end.year-start.year)*12+end.month-start.month)/3
	start_position = units_until_start_time*ts.line_length + ts.header_length
	byte_length = (units_until_end_time+1)*ts.line_length
	file.seek (start_position)
	raw_data = file.read (byte_length).rstrip ("\n\r")
	file.close ()
	lines = raw_data.split("\n")
	point_data = [line.strip("\r").split(",") for line in lines]

	ts.start = start
	ts.end = end

	for point in point_data:
		point[0] = datetime.strptime (point[0], "%Y-%m-%d")
		point[1] = float (point[1])
	ts.data = point_data
	return ts

def get_info (filename):
	f = open (filename, 'rU')
	ts = process_header (f)
	ts.filename = filename
	return ts

def format_row (row, series_index, n_series, padding):
	"""Takes in a row from a timeseries and adds in blank fields to either side of the data.
	"""
	space_before = series_index*(padding+1)
	space_after = padding + (n_series-series_index)*(padding+1)
	padding_left = [None for i in range(space_before)]
	padding_right = [None for i in range(space_after)]
	row[1:1] = padding_left
	row[2+space_before:2+space_before] = padding_right
	return row

def format_time_series (filenames,start,finish,padding=2):
	"""Get time series data for the same time period from multiple files

	Keyword arguments:
	filenames -- a list of filenames to load
	start -- the start time in datetime format
	finish -- the finish time in the same format
	padding -- how many empty fields to leave after each numeric field (for filling with annotation data)
	"""
	ts = TimeSeries ()
	n_series = len(filenames)-1
	for series_index, filename in enumerate(filenames):
		new_ts = get_time_series (filename, start, finish)
		i = 0 # A cursor for the original timeseries
		j = 0 # A cursor for the timeseries we're going to combine it with
		while i < len(ts.data) and j < len(new_ts.data):
			if new_ts.data[j][0] < ts.data[i][0]:
				new_row = format_row(new_ts.data[j], series_index, n_series, padding)
				ts.data.insert (i, new_row)
				j += 1
			else: i += 1
		while j < len(new_ts.data):
			new_row = format_row (new_ts.data[j], series_index, n_series, padding)
			ts.data.append (new_row)
			j += 1
	return ts
